import enum
import shelve
from xmlrpc.client import ServerProxy

from log import LogEntries
from state_machine import StateMachine


class PersistentState:

    def __init__(self, name, engine='_dbm'):
        self.name = name
        self.engine = engine

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.engine)[self.name]

    def __set__(self, obj, val):
        getattr(obj, self.engine)[self.name] = val

    def __delete__(self, obj):
        del getattr(obj, self.engine)[self.name]


class STATE(enum.Enum):
    """
    每个节点都处于这三个状态之一：leader follower candidate
    正常情况下, 集群中只有一个 leader, 且其他节点都是 follower
    follower 都是被动的, 他们不会发送任何请求, 只是简单的响应来自 leader 和 candidate 的请求
    """
    Follower = 'Follower'
    Leader = 'Leader'
    Candidate = 'Candidate'


class Node:

    # 服务器已知最新的任期（在服务器首次启动的时候初始化为0，单调递增）
    current_term = PersistentState('currentTerm')
    # 当前任期内收到选票的候选者id 如果没有投给任何候选者 则为空
    voted_for = PersistentState('votedFor')

    def __init__(self, ident, remote_servers, ticker):
        self._dbm = shelve.open('node_%s_state' % ident)
        self.ticker = ticker
        self.ident = ident
        self.current_term = 0
        self.voted_for = ''
        # 日志条目, 每个条目包含了用于状态机的命令, 以及领导者接收到该条目时的任期（第一个索引为1）
        self.log_entries = LogEntries(ident)

        self.state = STATE.Follower
        self.commit_index = 0  # 已知已提交的最高的日志条目的索引（初始值为0，单调递增）
        self.last_applied = 0  # 已经被应用到状态机的最高的日志条目的索引（初始值为0，单调递增）

        # 领导者（服务器）上的易失性状态 (选举后已经重新初始化)
        # nextIndex 对于每一台服务器，发送到该服务器的下一个日志条目的索引（初始值为领导者最后的日志条目的索引+1）
        # matchIndex 对于每一台服务器，已知的已经复制到该服务器的最高日志条目的索引（初始值为0，单调递增）
        self.remote_servers = {}
        for id_, addr in remote_servers.items():
            host, rpc_port = addr[0], addr[1]
            addr = f'http:{host}:{rpc_port}'
            self.remote_servers[id_] = {'nextIndex': 0, 'matchIndex': 0, 'addr': addr}

        self.state_machine = StateMachine(ident)
        self.leader_id = None  # 心跳发过来的leader_id
        self._remote_server_clients = {}

    def remote_server_client(self, ident):
        if ident not in self._remote_server_clients:
            self._remote_server_clients[ident] = ServerProxy(self.remote_servers[ident]['addr'])
        return self._remote_server_clients[ident]

    def info(self):
        return {
            'id': self.ident,
            'state': self.state.name,
            'leaderId': self.leader_id,
            'commitIndex': self.commit_index,
            'lastApplied': self.last_applied,
            'currentTerm': self.current_term,
            'votedFor': self.voted_for,
            'remoteServers': self.remote_servers
        }

    def append_log(self, entry):
        entry.term = self.current_term
        self.log_entries.append(entry)

    def request_vote(self):
        self.state = STATE.Candidate

    def reset_ticker(self):
        self.ticker.set()

    def ensure_leader(self) -> bool:
        """发送一次心跳, 确认自己是leader"""
        return self.state == STATE.Leader

    def create_rpc_service(self):
        return RPCService(self)


class RPCService:

    def __init__(self, node):
        self.node = node

    def append_entries(self, term, leader_id, prev_log_index, prev_log_term, entries, leader_commit):
        """追加条目RPC
        被领导者调用 用于日志条目的复制 同时也被当做心跳使用
        :param term: 领导者的任期
        :param leader_id: 领导者ID
        :param prev_log_index: 紧邻新日志条目之前的那个日志条目的索引
        :param prev_log_term: 紧邻新日志条目之前的那个日志条目的任期
        :param entries: 需要被保存的日志条目（被当做心跳使用是 则日志条目内容为空；为了提高效率可能一次性发送多个）
        :param leader_commit: 领导者的已知已提交的最高的日志条目的索引
        :return: (term, success)
        term: 当前任期,对于领导者而言 它会更新自己的任期
        success: 结果为真 如果跟随者所含有的条目和prevLogIndex以及prevLogTerm匹配上了
        """
        self.node.leader_id = leader_id
        for entry in entries:
            self.node.log_entries.append(entry)

        # TODO 重置成为Candidate的timeout
        return self.node.current_term, True

    def request_vote(self, term, candidate_id, last_log_index, last_log_term):
        """请求投票RPC
        由候选人负责调用用来征集选票
        :param term: 候选人任期号
        :param candidate_id: 请求选票的候选人的 Id
        :param last_log_index: 候选人的最后日志条目的索引值
        :param last_log_term: 候选人最后日志条目的任期号
        :return: (term, voteGranted)
        term: 当前任期号，以便于候选人去更新自己的任期号
        voteGranted: 候选人赢得了此张选票时为真
        """
        return self.node.current_term, True
